/*
 * Copyright 2011-2012 Rivoli team
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.googlecode.rivoli.model.support

import com.googlecode.rivoli.RivoliException
import com.googlecode.rivoli.model.AttributeDescriptor
import com.googlecode.rivoli.model.ElementDescriptor

class ElementDescriptorSupport implements ElementDescriptor {
	final String name
	private Set<AttributeDescriptor> attributeDescriptors
	private ElementDescriptor containerFor
	private ElementDescriptorSupport parent
	private Set<ElementDescriptorSupport> children
	private Set<?> renderers

	ElementDescriptorSupport(String name, Set<?> renderers = []) {
		this.name = name
		attributeDescriptors = [] as Set
		children = [] as Set
		this.renderers = renderers as Set
	}

	Set<AttributeDescriptor> getAttributeDescriptors() {
		Collections.unmodifiableSet(attributeDescriptors)
	}

	void addAttributeDescriptor(String name, Class<?> type) throws RivoliException {
		verifyAttributeDescriptorNames([name], findAttributeDescriptorNames(true, true))

		attributeDescriptors << new AttributeDescriptorSupport(name, type)
	}

	AttributeDescriptor findAttributeDescriptor(String name) {
		attributeDescriptors.find {it.name == name} ?: parent?.findAttributeDescriptor(name)
	}

	protected List<String> findAttributeDescriptorNames(boolean includeAncestors, boolean includeDescendants) {
		List<String> attributeDescriptorNames = attributeDescriptors*.name

		if (includeAncestors && parent) {
			attributeDescriptorNames += parent.findAttributeDescriptorNames(true, false)
		}

		if (includeDescendants) {
			children.each {ElementDescriptorSupport child ->
				attributeDescriptorNames += child.findAttributeDescriptorNames(false, true)
			}
		}

		attributeDescriptorNames
	}

	protected void verifyAttributeDescriptorNames(List<String> newNames, List<String> existingNames) throws RivoliException {
		List<String> duplicateNames = newNames.intersect(existingNames)

		if (!duplicateNames.empty) {
			throw new RivoliException("element descriptor '${name}' already contains attribute descriptor(s) ${duplicateNames}")
		}
	}

	ElementDescriptor getParent() {
		parent
	}

	void setParent(ElementDescriptorSupport parent) throws RivoliException {
		parent.verifyAttributeDescriptorNames(findAttributeDescriptorNames(false, true), parent.findAttributeDescriptorNames(true, false))
		verifyContainerFor(isContainer(false, true), parent.isContainer(true, false))

		this.parent = parent
		parent.addChild(this)
	}

	protected void addChild(ElementDescriptor child) {
		children << child
	}

	ElementDescriptor getContainerFor() {
		containerFor ?: parent?.containerFor
	}

	void setContainerFor(ElementDescriptorSupport containerFor) throws RivoliException {
		verifyContainerFor(true, isContainer(true, true))

		this.containerFor = containerFor
	}

	protected boolean isContainer(boolean includeAncestors, boolean includeDescendants) {
		containerFor || (includeAncestors && parent?.isContainer(true, false)) || (includeDescendants && children.any {ElementDescriptorSupport child ->
			child.isContainer(false, true)
		})
	}

	private void verifyContainerFor(boolean newContainer, boolean existingContainer) throws RivoliException {
		if (newContainer && existingContainer) {
			throw new RivoliException('only one element descriptor in a hierarchy can be a container')
		}
	}

	Object getRenderer(Class rendererType) {
		renderers.find {it in rendererType}
	}
}